#include "Object.h"

#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <SOIL.h>

#include <iostream>

vector<Light> Object::lights;
mat4 Object::viewMatrix;
mat4 Object::projMatrix;
double Object::gameTime;
GLuint Object::lastVAO = 0;
unsigned int Object::lastShader = 0;

Object::Object(Shader* shader, Model* model, Material* material) :
m_shader(shader),
m_model(model),
m_material(material),
m_posSet(true),
m_oriSet(true),
m_stateOk(false)
{
    init();
}

Object::~Object()
{
    glDeleteTextures(1, &m_texture0bump);
    glDeleteTextures(1, &m_texture0normal);
    glDeleteTextures(1, &m_texture0);
}

bool Object::init()
{   
    m_stateOk = false;

    if (m_shader == 0)
    {
        std::cerr << "No shader attached for object." << std::endl;        
        return m_stateOk;
    }

    if (m_model == 0)
    {
        std::cerr << "No model attached for object." << std::endl;
        return m_stateOk;
    }

    if (m_material == 0)
    {
        std::cerr << "No material attached for object." << std::endl;
        return m_stateOk;
    }

    glUseProgram(m_shader->id());

    if (m_material->getTexture() != "")
    {
        m_texture0 = loadTexture(m_material->getTexture().c_str());
        glUniform1i(glGetUniformLocation(m_shader->id(), "tex0"), 0);
    }

    if (m_material->getNormalmap() != "")
    {
        m_texture0normal = loadTexture(m_material->getNormalmap().c_str());
        glUniform1i(glGetUniformLocation(m_shader->id(), "tex0normal"), 1);
    }

    if (m_material->getBumpmap() != "")
    {
        m_texture0bump = loadTexture(m_material->getBumpmap().c_str());
        glUniform1i(glGetUniformLocation(m_shader->id(), "tex0bump"), 2);
    } 

    m_uniModel = glGetUniformLocation(m_shader->id(), "model");
    m_uniView = glGetUniformLocation(m_shader->id(), "view");
    m_uniModelView = glGetUniformLocation(m_shader->id(), "modelView");
    m_uniModelViewProj = glGetUniformLocation(m_shader->id(), "modelViewProj");

    m_stateOk = true;

    return m_stateOk;
}

void Object::update(const double timeElapsed)
{    
    // Transformations. Rotation and orientation are defined with a rotation vector.
    m_modelMatrix = glm::translate(m_modelMatrix, m_dir);

    if (glm::length(m_rot) != 0.0f)
    {
        m_modelMatrix = glm::rotate(m_modelMatrix, glm::length(m_rot), m_rot);
    }

    if (!m_posSet)
    {
        m_modelMatrix = glm::translate(m_modelMatrix, m_pos);
        m_posSet = true;
    }

    if (!m_oriSet && glm::length(m_ori) != 0.0f)
    {
        m_modelMatrix = glm::rotate(m_modelMatrix, glm::length(m_ori), m_ori);
        m_oriSet = true;
    }  

    m_modelViewMatrix = viewMatrix * m_modelMatrix;
    m_modelViewProjMatrix = projMatrix * viewMatrix * m_modelMatrix;
}

void Object::render()
{
    glBindVertexArray(m_model->getVAO());
    glUseProgram(m_shader->id());

    // Uniforms.
    glUniformMatrix4fv(m_uniModel, 1, GL_FALSE, glm::value_ptr(m_modelMatrix));
    glUniformMatrix4fv(m_uniView, 1, GL_FALSE, glm::value_ptr(viewMatrix));
    glUniformMatrix4fv(m_uniModelView, 1, GL_FALSE, glm::value_ptr(m_modelViewMatrix));
    glUniformMatrix4fv(m_uniModelViewProj, 1, GL_FALSE, glm::value_ptr(m_modelViewProjMatrix));

    GLuint location;
    string uniformName = "";

    location = glGetUniformLocation(m_shader->id(), "FCP");
    glUniform1f(location, FCP);
    
    location = glGetUniformLocation(m_shader->id(), "lightCount");
    glUniform1i(location, lights.size());

    for (unsigned int i = 0; i < lights.size(); ++i)    
    {
        uniformName = lights[i].getUniform(Light::POS);
        location = glGetUniformLocation(m_shader->id(), uniformName.c_str());
        glUniform3f(location, lights[i].getPos().x, lights[i].getPos().y, lights[i].getPos().z);

        uniformName = lights[i].getUniform(Light::COLOR);
        location = glGetUniformLocation(m_shader->id(), uniformName.c_str());
        glUniform3f(location, lights[i].getColor().x, lights[i].getColor().y, lights[i].getColor().z);

        uniformName = lights[i].getUniform(Light::POWER);
        location = glGetUniformLocation(m_shader->id(), uniformName.c_str());
        glUniform1f(location, lights[i].getPower());
    }

    // Materials.
    location = glGetUniformLocation(m_shader->id(), "ambientColor");
    glUniform3f(location, m_material->getAmbientColor().x, m_material->getAmbientColor().y, m_material->getAmbientColor().z);

    location = glGetUniformLocation(m_shader->id(), "diffuseColor");
    glUniform3f(location, m_material->getDiffuseColor().x, m_material->getDiffuseColor().y, m_material->getDiffuseColor().z);

    location = glGetUniformLocation(m_shader->id(), "specularColor");
    glUniform3f(location, m_material->getSpecularColor().x, m_material->getSpecularColor().y, m_material->getSpecularColor().z);

    location = glGetUniformLocation(m_shader->id(), "specularCoeff");
    glUniform1f(location, m_material->getSpecularCoeff());

    location = glGetUniformLocation(m_shader->id(), "alpha");
    glUniform1f(location, m_material->getAlpha());

    location = glGetUniformLocation(m_shader->id(), "bumpDepth");
    glUniform1f(location, m_material->getBumpDepth());

    location = glGetUniformLocation(m_shader->id(), "time");
    glUniform1f(location, static_cast<float>(gameTime));

    // Actual rendering.
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, m_texture0);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, m_texture0normal);
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, m_texture0bump);

    glDrawElements(GL_TRIANGLES, m_model->getBufferObjectSize(), GL_UNSIGNED_SHORT, (void*) 0);
}

void Object::setPosition(const vec3& pos)
{
    m_posSet = false;
    m_pos = pos;
}

void Object::setOrientation(const vec3& ori)
{
    m_oriSet = false;
    m_ori = ori;
}

void Object::setMovement(const vec3& dir)
{
    m_dir = dir;
}

void Object::setRotation(const vec3& rot)
{
    m_rot = rot;
}

vec3 Object::getPos() const
{
    return m_pos;
}

bool Object::isEmissive() const
{
    return m_material->isEmissive();
}

bool Object::isStateOk() const
{
    return m_stateOk;
}

GLuint Object::loadTexture(const char* path)
{
    GLuint texture;
	glGenTextures(1, &texture);
	
	int width, height;
	unsigned char* image;
	
	glBindTexture(GL_TEXTURE_2D, texture);
	image = SOIL_load_image(path, &width, &height, 0, SOIL_LOAD_RGB);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
	SOIL_free_image_data(image);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	return texture;
}